package com.example.demo.utils;

import com.example.demo.vo.task.TaskVO;
import com.example.demo.vo.user.UserVO;
import org.apdplat.word.vector.T;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Recommender {
    private static Recommender instance;
    private Recommender(){}
    private boolean isAbility = false;
    private boolean isPreference = false;
    private boolean isDevice = true;

    public static Recommender getInstance(){
        if(instance==null) {
            instance = new Recommender();
        }
        return instance;
    }

    public void setStrategy(boolean ability,boolean preference,boolean device){
        this.isAbility=ability;
        this.isPreference=preference;
        this.isDevice=device;
    }

    public String getStrategy(){
        String strategy="";
        strategy+=isAbility?"1":"0";
        strategy+=isPreference?"1":"0";
        strategy+=isDevice?"1":"0";
        return strategy;
    }

    public List<TaskVO> getRecommendedTasks(UserVO userVO, List<TaskVO> tasksToRecommend){
        for (TaskVO taskVO : tasksToRecommend) {
            taskVO.setSimilarity(calculate(userVO, taskVO));
        }
        Collections.sort(tasksToRecommend,
                new Comparator<TaskVO>() {
                    @Override
                    public int compare(TaskVO o1, TaskVO o2) {
                        if(o1.getSimilarity()<o2.getSimilarity()){
                            return 1;
                        }else if(o1.getSimilarity()==o2.getSimilarity()){
                            return 0;
                        }else {
                            return -1;
                        }
                    }
                });
        List<TaskVO> res = new ArrayList<>();
        for(int i=0;i<Math.min(10,tasksToRecommend.size());i++){
            res.add(tasksToRecommend.get(i));
        }
        return res;
    }

    public double calculate(UserVO userVO,TaskVO taskVO){
        ArrayList<Double> userData = new ArrayList<>();
        ArrayList<Double> taskData = new ArrayList<>();
        double ability = userVO.getAbility()/10.0;
        double preference = 0;
        double totalPre = userVO.getPreferenceFunction()+userVO.getPreferencePerformance();
        double device = 0;
        double totalDev = userVO.getDeviceAndroid()+userVO.getDeviceIos()+userVO.getDeviceWindows()+userVO.getDeviceMac()+userVO.getDeviceLinux();
        if(totalPre==0){
            preference = 0.5;
        }else{
            switch (taskVO.getType()){
                case 1:
                    preference = (double)userVO.getPreferencePerformance()/totalPre;
                    break;
                case 0:
                    preference = (double)userVO.getPreferenceFunction()/totalPre;
            }
        }
        if(totalDev==0){
            device=0.2;
        }else {
            switch (taskVO.getDevice()) {
                case 0:
                    device = userVO.getDeviceWindows() / totalDev;
                    break;
                case 1:
                    device = userVO.getDeviceMac() / totalDev;
                    break;
                case 2:
                    device = userVO.getDeviceLinux() / totalDev;
                    break;
                case 3:
                    device = userVO.getDeviceAndroid() / totalDev;
                    break;
                case 4:
                    device = userVO.getDeviceIos() / totalDev;
                    break;

            }
        }
        double difficulty = taskVO.getDifficulty()/10.0;
        // 将数据写入数组
        userData = createListByStrategy(ability,preference,device);
        taskData = createListByStrategy(difficulty,1.0,1.0);
        //单因素的时候使用曼哈顿距离
        if((isAbility && !isDevice && !isPreference)||(!isAbility&&isPreference&&!isDevice)||(!isAbility&&!isPreference&&isDevice)){
            return calculateManhattanDistance(userData,taskData);
        }
        return calculateCosineSimilarity(userData,taskData);//保证负相关
    }

    public double calculateEuclideanDistance(ArrayList<Double> userData, ArrayList<Double> taskData){
        int size = userData.size();
        double sigma = 0.0;
        double res = 0.0;
        for(int i=0;i<size;i++){
            sigma+=Math.pow(userData.get(i)-taskData.get(i),2);
        }
        res = Math.sqrt(sigma);
        if(res==0){
            return Double.MAX_VALUE;
        }
        return 1/res;
    }

    public double calculateManhattanDistance(ArrayList<Double> userData, ArrayList<Double> taskData){
        int size = userData.size();
        double distance = 0.0;
        for(int i=0;i<size;i++){
            distance += Math.abs(userData.get(i)-taskData.get(i));
        }
        if(distance==0){
            return Double.MAX_VALUE;
        }
        return 1/distance;
    }

    public double calculateCosineSimilarity(ArrayList<Double> userData, ArrayList<Double> taskData){
        int size = userData.size();
        double up = 0.0;
        double downU = 0.0;
        double downT = 0.0;
        double res = 0.0;
        for(int i=0;i<size;i++){
            up += userData.get(i)*taskData.get(i);
            downU += Math.pow(userData.get(i),2);
            downT += Math.pow(taskData.get(i),2);
        }
        if(downU==0||downT==0){
            return -1;
        }
        res = up/(Math.sqrt(downU)*Math.sqrt(downT));
        return res;
    }


//    public double sigmoidFunc(double x){
//        double ey = Math.pow(Math.E,-x);
//        return 1/(1+ey);
//    }

    ArrayList<Double> createListByStrategy(double ability,double preference,double device){
        ArrayList<Double> res = new ArrayList<>();
        if(isAbility&&isPreference&&isDevice){
            res.add(ability);
            res.add(preference);
            res.add(device);
        }else if(isAbility&&isPreference){
            res.add(ability);
            res.add(preference);
        }else if(isAbility&&isDevice){
            res.add(ability);
            res.add(device);
        }else if(isDevice&&isPreference){
            res.add(device);
            res.add(preference);
        }else if(isAbility){
            res.add(ability);
        }else if(isPreference){
            res.add(preference);
        }else {
            res.add(device);
        }
        return res;
    }


}
